package client;

import gameframe.Chess;
import gameframe.GameFrame;
import gameframe.ChessPanel;
import gameframe.ChatPanel;
import gameframe.LoginFrame;
import gamelobby.GameLobby;
import server.Model;
import musicthread.MusicThread;

import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.net.Socket;
import java.util.ArrayList;
import java.util.LinkedList;

/**
 * @ClassName:ClientPlayer
 * @author Hsu琛君珩
 * @date 2023-12-22
 * @apiNote
 * @Version: v1.0
 */

/**
 * 请求控制命令，包含各种指令的常量定义，用于客户端与服务器之间的通信。
 */
class CommandOption {
    public static final String GET_ROOM_MEMBER_NUM = "GET_ROOM_MEMBER_NUM";  // 请求房间人数信息
    public static final String REGISTER_VERIFY = "REGISTER_VERIFY";  // 请求注册验证
    public static final String LOGIN_VERIFY = "LOGIN_VERIFY";  // 请求登录验证
    public static final String CHECK_UPDATE = "CHECK_UPDATE";  // 请求更新命令
    public static final String PUT_CHESS = "PUT_CHESS";  // 下棋命令
    public static final String REGRET_CHESS = "REGRET_CHESS";  // 悔棋命令
    public static final String AGREE_REGRET = "AGREE_REGRET";  // 同意悔棋命令
    public static final String SEND_CHET_MESSAGE = "SEND_CHET_MESSAGE";  // 发送聊天信息命令
    public static final String WIN = "WIN";  // 获胜命令
    public static final String SURRENDER = "SURRENDER";  // 认输命令
    public static final String EXIT = "EXIT";  // 退出命令
}


/**
 *
 * 客户端玩家类，负责处理与服务器的通信，管理游戏状态，以及用户界面的交互。
 */
public class ClientPlayer extends Component {
    private static ClientPlayer instance;  // 单例实例
    private String userName;  // 用户名
    private int chessColor;  // 用户棋子颜色
    private int roomID;  // 房间号
    private int roomPort;  // 房间端口号
    private Socket socket;  // 通信套接字
    private Model model;  // 棋局模型
    private boolean isGaming = false;  // 是否正在游戏
    private boolean isTurn;  // 是否轮到本方下棋

    /**
     * 获取客户端玩家单例实例。
     * @return ClientPlayer的单例实例。
     */
    public static ClientPlayer getInstance() {
        if (instance == null) {
            instance = new ClientPlayer();
        }
        return instance;
    }

    /**
     * 获取玩家的棋子颜色。
     * 这个颜色决定玩家在游戏中扮演黑方或白方。
     * @return 棋子颜色，黑或白。
     */
    public int getChessColor() {
        return this.chessColor;
    }

    /**
     * 设置玩家的用户名。
     * 这个用户名在游戏和聊天中标识玩家。
     * @param userName 玩家选择的用户名。
     */
    public void setUserName(String userName) {
        this.userName = userName;
    }

    /**
     * 获取用户名。
     * 用于在游戏中显示和在聊天中标识玩家。
     * @return 当前设置的用户名。
     */
    public String getUserName() {
        return userName;
    }

    /**
     * 设置玩家的房间号。
     * 玩家在加入游戏时会被分配到一个房间，这个房间号标识了那个房间。
     * @param roomID 玩家所在房间的编号。
     */
    public void setRoomID(int roomID) {
        this.roomID = roomID;
        System.out.println(roomID);
    }

    /**
     * 检查玩家是否正在游戏中。
     * 这可以用来决定玩家是否可以开始新游戏或加入游戏。
     * @return 如果玩家正在游戏中则返回true，否则返回false。
     */
    public boolean isGaming() {
        return isGaming;
    }

    /**
     * 检查是否轮到玩家下棋。
     * 在每次对手下棋后，应检查该方法以决定是否轮到当前玩家。
     * @return 如果轮到玩家则返回true，否则返回false。
     */
    public boolean isTurn() {
        return this.isTurn;
    }

    /**
     * 和主服务器建立连接。
     */
    public void connectMainServer() {
        try {
            socket = new Socket("localhost", 8080);
            System.out.println("客户端线程启动，连接主服务器成功。");
        } catch (IOException e) {
            e.printStackTrace();
            JOptionPane.showMessageDialog(null, "无法连接到主服务器。", "连接错误", JOptionPane.ERROR_MESSAGE);
        }
    }

    /**
     * 进入登录界面。
     */
    private void login() {
        LoginFrame.getInstance().setVisible(true);
    }

    /**
     * 用户注册验证。
     * 此方法用于客户端向服务器发送注册请求并接收验证结果。
     *
     * @param userName 用户希望注册的用户名。
     *                 用户名是用户的唯一标识，需要在服务器数据库中是唯一的。
     * @param password 用户的密码的哈希值。
     *                 出于安全考虑，密码在客户端进行哈希处理后再网络中传输，
     *                 这样即使传输过程被监听，也无法直接获得用户的原始密码。
     * @return 如果服务器确认用户名是唯一的，且成功将用户信息写入数据库，则返回true表示注册成功。
     *         如果发生网络错误或用户名已存在，则返回false表示注册失败。
     */
    public boolean registerVerify(String userName, String password) {
        try {
            // 尝试与服务器的指定端口建立Socket连接
            socket = new Socket("localhost", 8080);
            // 获取Socket的输出流，用于发送注册请求
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            // 发送注册请求。格式为 "REGISTER_VERIFY:用户名:密码哈希"
            out.println(CommandOption.REGISTER_VERIFY + ":" + userName + ":" + password);
            // 获取Socket的输入流，用于接收服务器的响应
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            // 读取服务器的响应
            String response = in.readLine();
            // 判断服务器的响应是否为"TRUE"，是则返回true表示注册成功
            return "TRUE".equals(response);
        } catch (IOException e) {
            // 网络错误处理，打印异常堆栈跟踪信息并返回false表示注册失败
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 通过向服务器发送请求并接收响应来验证用户登录。
     * 此方法用于核实用户凭据是否与服务器记录匹配。
     *
     * @param userName 用户尝试登录时提供的用户名。
     *                 用户名是每个用户的唯一标识符，必须与服务器数据库中存储的相匹配。
     * @param password 用户密码的哈希值。
     *                 为了增强安全性，在网络传输前对实际密码进行哈希处理。这意味着即使传输被截获，
     *                 截获者也无法得到用户的实际密码。
     * @return 如果服务器确认用户名和密码与其记录匹配，返回true，表示登录成功。
     *         如果出现网络错误或用户名和密码不匹配，返回false，表示登录失败。
     */
    public boolean loginVerify(String userName, String password) {
        try {
            // 尝试建立到指定端口服务器的连接。
            socket = new Socket("localhost", 8080);
            // 获取socket的输出流，用于向服务器发送登录请求。
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            // 发送登录请求。格式为 "LOGIN_VERIFY:用户名:密码哈希"。
            out.println(CommandOption.LOGIN_VERIFY + ":" + userName + ":" + password);
            // 获取socket的输入流，用于读取服务器的响应。
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            // 读取服务器的响应。
            String response = in.readLine();
            // 检查服务器的响应是否为"TRUE"，表示登录成功。
            return "TRUE".equals(response);
        } catch (IOException e) {
            // 处理网络错误，打印堆栈跟踪信息，并返回false表示登录失败。
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 进入游戏大厅。
     */
    public void enterGameLobby() {
        System.out.println(userName + " 进入游戏大厅。");
        GameLobby.getInstance().start();
    }

    /**
     * 从服务器获取当前所有房间的成员数量列表。
     * 该方法通过向服务器发送特定请求来获取每个房间内的玩家数，用于更新和显示在游戏大厅的房间信息。
     *
     * @return 返回一个包含每个房间成员数量的整数列表。如果出现网络错误或其他问题，返回一个空的列表。
     */
    public ArrayList<Integer> getRoomList() {
        try {
            // 建立到主服务器的连接。
            socket = new Socket("localhost", 8080);
            // 向服务器发送获取房间成员数量的请求。
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            out.println(CommandOption.GET_ROOM_MEMBER_NUM);
            // 接收服务器返回的房间成员数量列表。
            ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
            // 将接收到的对象转换为ArrayList<Integer>类型并返回。
            return (ArrayList<Integer>) in.readObject();
        } catch (IOException | ClassNotFoundException e) {
            // 处理网络错误或类转换错误，打印堆栈跟踪信息，并返回一个空的列表。
            e.printStackTrace();
            return new ArrayList<>();
        }
    }

    /**
     * 初始化玩家的基本信息，包括所在房间号和棋子颜色。
     * 这个方法为玩家的游戏准备阶段提供了必要的设置，包括连接房间服务器和进入游戏。
     *
     * @param roomId 玩家选择进入的房间号。每个房间是独立的游戏空间，具有唯一的房间号。
     * @param chessColor 玩家在游戏中的棋子颜色。这决定了玩家是执黑棋还是执白棋，或者是观战者。
     */
    public void init(int roomId, int chessColor) {
        // 设置玩家的房间号和棋子颜色。
        this.roomID = roomId;
        this.chessColor = chessColor;
        // 根据棋子颜色确定是否轮到玩家下棋。如果玩家执黑棋，则首先下棋。
        isTurn = chessColor == Chess.BLACK;
        // 连接到指定房间的服务器。
        connectRoomServer();
        // 进入游戏，开始游戏的主循环，等待玩家操作或接收游戏状态更新。
        enterGame();
    }

    /**
     * 与指定房间的服务器建立连接。
     * 此方法首先向主服务器发送请求，获取加入特定房间所需的端口号，然后使用这个端口号与房间服务器建立连接。
     */
    private void connectRoomServer() {
        try {
            // 创建一个新的Socket连接到主服务器。
            socket = new Socket("localhost", 8080);
            // 发送请求给主服务器以获取加入房间的端口号。
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            out.println("SET_ROOMID:" + roomID);
            // 读取主服务器返回的房间端口号。
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String line = in.readLine();
            // 如果返回的消息是以"a:"开头的，那么解析并设置房间端口号。
            if (line.startsWith("a:")) {
                roomPort = Integer.parseInt(line.substring(2));
                System.out.println("加入房间的端口号: " + roomPort);
            }
        } catch (IOException e) {
            // 如果在连接过程中出现任何IO异常，打印异常堆栈跟踪信息。
            e.printStackTrace();
        }
    }

    /**
     * 进入游戏界面，并开始与房间服务器进行通信。
     * 此方法将游戏标记为正在进行状态，显示游戏界面，并启动一个新线程不断地检查游戏状态的更新。
     */
    private void enterGame() {
        // 标记玩家当前正在游戏中。
        isGaming = true;
        // 显示游戏界面。
        GameFrame.getInstance().setVisible(true);
        // 创建并启动一个新线程，该线程将循环运行，定期从房间服务器获取游戏状态更新。
        new Thread(() -> {
            System.out.println("开始检查棋局更新。");
            while (isGaming) { // 只要玩家仍在游戏中，就不断检查更新。
                checkUpdate(); // 检查并处理游戏状态的更新。
                try {
                    // 每次检查后，线程暂停一段时间，以限制更新频率，减少服务器负载。
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    // 如果在休眠期间线程被中断，打印异常堆栈跟踪信息。
                    e.printStackTrace();
                }
            }
        }).start();
        // 启动背景音乐。
        MusicThread.getInstance().startBackgroundMusic();
    }

    /**
     * 检查棋局状态是否发生更新。
     * 这个方法会向房间服务器发送一个请求以获取最新的游戏状态，
     * 包括棋盘上的棋子分布和当前的聊天信息。
     * 它会根据返回的信息更新本地的游戏状态，并根据游戏情况做出相应的反应。
     */
    private void checkUpdate() {
        try {
            // 创建一个新的Socket连接到房间服务器。
            socket = new Socket("localhost", roomPort);
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);

            // 向服务器发送请求，以获取当前棋局的最新状态。
            out.println(CommandOption.CHECK_UPDATE);

            // 读取服务器返回的游戏状态对象。
            ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
            model = (Model) in.readObject();

            // 从服务器返回的对象中提取棋子栈和聊天信息，并更新本地视图。
            LinkedList<Chess> chessStack = model.getChessStack();
            String chatInfo = model.getChatInfo();
            ChessPanel.getInstance().update(chessStack, chatInfo);

            // 判断是否轮到当前玩家下棋。
            if ((!chessStack.isEmpty()) &&
                    (chessStack.getLast().getColor() != this.chessColor) &&
                    (this.chessColor != Chess.SPACE)) {
                this.isTurn = true;
            }

            // 根据服务器返回的胜负情况做出相应的反应。
            if (model.getWinner() != Chess.SPACE) {
                handleGameEnd(model.getWinner());
                GameFrame.getInstance().showGameOver(model.getWinner());
            }

            // 如果收到悔棋请求或认输请求，询问用户是否同意。
            if ((model.getRegretChessColor() != Chess.SPACE) &&
                    model.getRegretChessColor() == (-1 * this.chessColor)) {
                askAgreeRegret();
            }
            if ((model.getSurrenderChessColor() != Chess.SPACE) &&
                    model.getSurrenderChessColor() == (-1 * this.chessColor)) {
                askAgreeSurrender();
            }
        } catch (IOException | ClassNotFoundException e) {
            // 在读取游戏状态或更新本地视图时出现异常。
            e.printStackTrace();
        }
    }

    /**
     * 根据赢家处理游戏结束。
     * 当检测到游戏结束时，此方法将被调用以显示相应的消息并重置游戏状态。
     * @param winner 获胜玩家的颜色。
     */
    private void handleGameEnd(int winner) {
        // 如果当前玩家是赢家，则显示获胜消息。
        if (winner == this.chessColor) {
            GameFrame.getInstance().showWin();
            this.isGaming = false;
            this.isTurn = false;
        } else {
            // 如果当前玩家不是赢家，则显示失败消息。
            GameFrame.getInstance().showLose();
            this.isGaming = false;
            this.isTurn = false;
        }
        // 游戏结束后，准备进行复盘或退出。
        initRePlay();
    }

    /**
     * 准备复盘操作
     */
    private void initRePlay() {
        ChatPanel.getInstance().initRePlay();
    }

    /**
     * 弹出对话框询问用户是否同意对方的悔棋请求。
     * 如果用户同意，则发送确认消息给服务器；如果不同意，则发送拒绝消息。
     */
    private void askAgreeRegret() {
        // 弹出对话框询问用户是否同意对方的悔棋请求。
        int option = JOptionPane.showConfirmDialog(null, "对方请求悔棋，懦了，是否同意？", "悔棋请求", JOptionPane.YES_NO_OPTION);

        // 根据用户的选择，发送同意或不同意悔棋的消息给服务器。
        if (option == JOptionPane.YES_OPTION) {
            sendAgreeRegretMessage(1);  // 用户选择同意悔棋
        } else {
            sendAgreeRegretMessage(0);  // 用户选择不同意悔棋
        }
    }

    /**
     * 向房间服务器发送是否同意悔棋的消息。
     * @param option 用户的选择，1表示同意，0表示不同意。
     */
    private void sendAgreeRegretMessage(int option) {
        try {
            // 建立与房间服务器的连接。
            socket = new Socket("localhost", roomPort);
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);

            // 发送同意或不同意悔棋的命令到服务器。
            out.println(CommandOption.AGREE_REGRET + ":" + option);
        } catch (IOException e) {
            // 处理网络通信异常。
            e.printStackTrace();
        }
    }

    /**
     * 弹出对话框询问用户是否确认对方的认输。
     * 如果用户确认，则发送胜利消息给服务器，标志着游戏结束。
     */
    private void askAgreeSurrender() {
        // 弹出对话框询问用户是否确认对方的认输。
        int option = JOptionPane.showConfirmDialog(null, "对方请求认输，懦了，是否接受？", "认输请求", JOptionPane.YES_NO_OPTION);

        // 如果用户确认对方的认输，发送胜利消息给服务器。
        if (option == JOptionPane.YES_OPTION) {
            sendWinMessage(this.chessColor);  // 确认认输，发送胜利消息
        }
    }

    /**
     * 玩家下棋操作。
     * 在客户端发起下棋请求，将棋子放置在棋盘上的指定位置，并从服务器获取更新后的棋子栈。
     * @param chessColor 棋子的颜色，用于区分是黑棋还是白棋。
     * @param curX 棋子放置的横坐标。
     * @param curY 棋子放置的纵坐标。
     * @return 返回更新后的棋子栈，包含所有已下的棋子。
     */
    public LinkedList<Chess> putChess(int chessColor, int curX, int curY) {
        // 确保当前轮到玩家下棋，如果不是则显示错误消息。
        //if (!isTurn) {
        //    JOptionPane.showMessageDialog(null, "还没轮到你！", "下棋错误", JOptionPane.ERROR_MESSAGE);
        //    return null;
        //}
        try {
            this.isTurn = false; // 重置玩家的下棋状态为未轮到。
            // 建立与房间服务器的连接。
            socket = new Socket("localhost", roomPort);
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            // 向服务器发送下棋命令和棋子信息。
            out.println(CommandOption.PUT_CHESS + ":" + chessColor + ":" + curX + ":" + curY);

            // 读取服务器返回的更新后的棋子栈。
            ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
            LinkedList<Chess> chessStack = (LinkedList<Chess>) in.readObject();
            System.out.println("收到服务端返回的棋子栈: " + chessStack);

            // 检查当前下的棋子是否导致了胜利，并相应地发送胜利消息。
            if (checkWin(curX, curY, chessColor)) {
                sendWinMessage(this.chessColor);
            }

            return chessStack; // 返回更新后的棋子栈。
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace(); // 打印异常信息。
            JOptionPane.showMessageDialog(null, "与服务器通信出错！", "网络错误", JOptionPane.ERROR_MESSAGE);
            return null; // 发生异常时返回null。
        }
    }

    /**
     * 向房间服务器发送胜利消息。
     * 当玩家胜利时调用此方法，通知服务器当前玩家已赢得游戏。
     * @param chessColor 获胜方的棋子颜色，用于区分是黑棋还是白棋。
     */
    private void sendWinMessage(int chessColor) {
        try {
            // 建立与房间服务器的连接。
            socket = new Socket("localhost", roomPort);
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            // 向服务器发送胜利命令，包括获胜方的棋子颜色。
            out.println(CommandOption.WIN + ":" + chessColor);
        } catch (IOException e) {
            e.printStackTrace(); // 打印异常信息。
        }
    }

    /**
     * 检查当前是否有胜负发生。
     * 通过检查放置的棋子周围是否形成了五子连线来判断玩家是否赢得游戏。
     * @param curX 当前放置棋子的x坐标（棋盘格）。
     * @param curY 当前放置棋子的y坐标（棋盘格）。
     * @param chessColor 当前放置的棋子颜色。
     * @return 如果当前玩家赢了则返回true，否则返回false。
     */
    private boolean checkWin(int curX, int curY, int chessColor) {
        int[] directionsX = {0, 1, 1, 1, 0, -1, -1, -1}; // 8个方向的x坐标变化。
        int[] directionsY = {1, 1, 0, -1, -1, -1, 0, 1}; // 8个方向的y坐标变化。
        int[] countInDirection = new int[8]; // 记录8个方向上连续的同色棋子数。

        int[][] chessBoard = model.getBoard(); // 获取棋盘状态。
        // 检查棋子周围8个方向的连续同色棋子数。
        for(int i = 0; i < directionsX.length; i++) {
            int steps = 1; // 从当前棋子开始计数。
            // 继续在当前方向上寻找相同颜色的棋子。
            while(checkIndex(curX + directionsX[i] * steps, curY + directionsY[i] * steps)
                    && chessBoard[curX + directionsX[i] * steps][curY + directionsY[i] * steps] == chessColor) {
                countInDirection[i]++;
                steps++;
            }
        }
        // 检查是否存在5子连线：四个方向对，每对方向上的棋子数加起来再加上当前棋子本身。
        for(int i = 0; i < 4; i++) {
            if(countInDirection[i] + countInDirection[i + 4] + 1 >= 5) {
                return true; // 如果在任意方向上连续棋子数达到5，则当前玩家赢。
            }
        }
        return false; // 否则游戏继续。
    }

    /**
     * 检查指定的棋盘格是否在棋盘范围内，并且没有被其他棋子占据。
     *
     * @param curX 检查位置的x坐标（棋盘格）
     * @param curY 检查位置的y坐标（棋盘格）
     * @return 如果位置有效且未被占据，则返回true
     */
    public boolean checkIndex(int curX, int curY) {
        return model.checkIndex(curX, curY);
    }

    /**
     * 重载的checkIndex方法，提供更多定制的检查，例如忽略占位检查。
     *
     * @param curX 检查位置的x坐标（棋盘格）
     * @param curY 检查位置的y坐标（棋盘格）
     * @param i 额外参数，用于定制检查逻辑
     * @return 根据定制逻辑返回检查结果
     */
    public boolean checkIndex(int curX, int curY, int i) {
        // 此处可以根据需要添加更多的检查逻辑
        return model.checkIndex(curX, curY, i);
    }

    /**
     * 向房间服务器发送聊天信息。
     * 当用户在聊天界面输入信息并发送时，此方法会被调用来处理信息的发送。
     * @param inputStr 用户输入的聊天信息文本。
     */
    public void sendChatMessage(String inputStr) {
        try {
            // 创建到房间服务器的新连接。
            socket = new Socket("localhost", roomPort);
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            // 发送包含用户名和聊天信息的命令到服务器。
            out.println(CommandOption.SEND_CHET_MESSAGE + ":" + userName + ":" + inputStr);
        } catch (IOException e) {
            e.printStackTrace(); // 打印异常信息。
        }
    }

    /**
     * 向房间服务器发送悔棋请求。
     * 当用户在游戏过程中选择悔棋时，此方法将被调用，向服务器发送悔棋请求。
     */
    public void regretChess() {
        try {
            // 创建到房间服务器的新连接。
            socket = new Socket("localhost", roomPort);
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            // 向服务器发送悔棋命令，包含当前玩家的棋子颜色。
            out.println(CommandOption.REGRET_CHESS + ":" + chessColor);
            System.out.println("客户端请求悔棋。"); // 在控制台记录悔棋请求。
        } catch (IOException e) {
            e.printStackTrace(); // 打印异常信息。
        }
    }

    /**
     * 向房间服务器发送认输请求。
     * 当用户选择认输，结束当前游戏时，此方法会被调用，向服务器发送认输请求。
     */
    public void surrender() {
        try {
            // 创建到房间服务器的新连接。
            socket = new Socket("localhost", roomPort);
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            // 向服务器发送认输命令，包含当前玩家的棋子颜色。
            out.println(CommandOption.SURRENDER + ":" + chessColor);
            System.out.println("客户端请求认输。"); // 在控制台记录认输请求。
        } catch (IOException e) {
            e.printStackTrace(); // 打印异常信息。
        }
    }

    /**
     * 本地进行复盘操作。
     */
    public void reShow() {
        ChessPanel.getInstance().reShow();
    }

    /**
     * 完成复盘操作。
     * 当用户完成复盘，即逐步回顾完所有走棋步骤后，调用此方法。
     * 弹出提示框告知用户复盘操作已完成。
     */
    public void finishReshow() {
        JOptionPane.showMessageDialog(GameFrame.getInstance(), "已经完成复盘操作。鳖甲装努力学习了！", "复盘结束", JOptionPane.OK_OPTION);
    }

    /**
     * 返回游戏大厅，并向主服务器发送退出游戏进入游戏大厅的请求。
     * 如果玩家当前正在游戏中，则提示不能返回；如果不在游戏中，将关闭游戏界面，显示游戏大厅，并发送退出游戏的请求到服务器。
     */
    public void returnGameLobby() {
        if (isGaming) {
            // 如果玩家正在游戏中，弹出提示框告知不能返回游戏大厅。
            JOptionPane.showMessageDialog(GameFrame.getInstance(), "您正在游戏中，不能返回。懦手！", "返回游戏大厅", JOptionPane.OK_OPTION);
        } else {
            // 设置游戏状态为非游戏中，关闭游戏界面，显示游戏大厅界面。
            isGaming = false;
            isTurn = false;
            GameFrame.getInstance().setVisible(false);
            GameLobby.getInstance().setVisible(true);
            sendExitMessage(); // 向服务器发送退出游戏的请求。
            GameLobby.getInstance().start(); // 重新启动游戏大厅的更新线程。
        }
    }

    /**
     * 向主服务器发送退出游戏进入大厅请求。
     * 当玩家选择返回游戏大厅时，调用此方法来通知服务器玩家已退出当前房间。
     */
    private void sendExitMessage() {
        try {
            // 建立与主服务器的连接。
            socket = new Socket("localhost", 8080);
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            // 发送退出当前房间的命令，包含房间ID。
            out.println(CommandOption.EXIT + ":" + roomID);
        } catch (IOException e) {
            e.printStackTrace(); // 打印任何异常信息。
        }
    }

    /**
     * 主程序入口。
     * 和主服务器建立连接并进入登录界面。
     * @param args 命令行参数
     */
    public static void main(String[] args) {
        ClientPlayer player = ClientPlayer.getInstance();
        player.connectMainServer();
        player.login();
    }
}